/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package org.finjic.xml;

import org.finjic.entity.Argument;
import org.finjic.entity.BeanDefinition;
import org.finjic.entity.ConstructorArg;
import org.finjic.exception.DuplicateBeanIdException;
import org.finjic.type.ArgumentType;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import java.util.*;
import org.finjic.type.ScopeType;

/**
 * @author apets
 */
public class ConfigSaxHandler extends DefaultHandler {

    private Set<BeanDefinition> allBeans = new HashSet<BeanDefinition>();
    private Set<String> beanIds = new HashSet<String>();
    private BeanDefinition currentBean;
    private String currentPropertyName = null;
    private boolean readingPropertyDefState = false;
    private Object currentPropertyDef = null;
    private ArgumentType currentPropertyType = null;

    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
        if (qName.equalsIgnoreCase("bean")) {
            String id = attributes.getValue("id");
            if (beanIds.contains(id)) {
                throw new DuplicateBeanIdException(id);
            }
            currentBean = new BeanDefinition(id, attributes.getValue("class"));
            currentBean.setFactoryBean(attributes.getValue("factoryBean"));
            currentBean.setFactoryClass(attributes.getValue("factoryClass"));
            currentBean.setFactoryMethod(attributes.getValue("factoryMethod"));
            if (attributes.getValue("factory-method") != null) {
                currentBean.setFactoryMethod(attributes.getValue("factory-method"));
                if (attributes.getValue("factory-bean") != null) {
                    currentBean.setFactoryBean(attributes.getValue("factory-bean"));
                }
            }
            String lazyinit = attributes.getValue("lazy-init");
            if (lazyinit != null) {
                if (lazyinit.equalsIgnoreCase("true")) {
                    currentBean.setLazy(true);
//                    System.out.println("lazy");
                }
            }
            String scope = attributes.getValue("scope");
            if (scope != null) {
                if (scope.equalsIgnoreCase("singleton")) {
                    currentBean.setScope(ScopeType.SINGLETON);
//                    System.out.println("single");
                } else if (scope.equalsIgnoreCase("prototype")) {
                    currentBean.setScope(ScopeType.PROTOTYPE);
//                    System.out.println("proto");
                }
            }

        } else if (qName.equalsIgnoreCase("property")) {
            Argument arg = createArgument(attributes);
            Map<String, Argument> currentBeanProperties = currentBean.getProperties();
            if (currentBeanProperties == null) {
                currentBeanProperties = new HashMap<String, Argument>();
                currentBean.setProperties(currentBeanProperties);
            }
            if (arg != null) {
                currentBeanProperties.put(attributes.getValue("name"), arg);
            } else {
                currentPropertyName = attributes.getValue("name");
            }
        } else if (qName.equalsIgnoreCase("list")) {
            readingPropertyDefState = true;
            currentPropertyType = ArgumentType.LIST;
            currentPropertyDef = new LinkedList<Argument>();
        } else if (qName.equalsIgnoreCase("set")) {
            readingPropertyDefState = true;
            currentPropertyType = ArgumentType.SET;
            currentPropertyDef = new HashSet<Argument>();
        } else if (qName.equalsIgnoreCase("MAP")) {
            readingPropertyDefState = true;
            currentPropertyType = ArgumentType.MAP;
            currentPropertyDef = new HashMap<String, Argument>();
        } else if (qName.equalsIgnoreCase("PROPERTIES")) {
            readingPropertyDefState = true;
            currentPropertyType = ArgumentType.PROPERTIES;
            currentPropertyDef = new Properties();
        }
        else if (readingPropertyDefState){ //"entry" tag
            processNextCollectionValue(attributes);
        } 
        
        else if (qName.equalsIgnoreCase("constructor-arg")) {
            ConstructorArg arg = new ConstructorArg(createArgument(attributes));
            if (attributes.getValue("name") != null) {
                arg.setName(attributes.getValue("name"));
            } else {
                String indexStr = attributes.getValue("index");
                if (indexStr != null) {
                    Integer index = Integer.parseInt(indexStr);
                    arg.setIndex(index);
                }
            }
            List<ConstructorArg> constructorArgs = currentBean.getConstructorArgs();
            if(constructorArgs==null){
                constructorArgs = new LinkedList<ConstructorArg>();
            }
            constructorArgs.add(arg);
        }
    }

    public void endElement(String uri, String localName, String qName) throws SAXException {
        if (qName.equalsIgnoreCase("bean")) {
            List<ConstructorArg> constructorArgs = currentBean.getConstructorArgs();
            if (constructorArgs != null && !constructorArgs.isEmpty()) {
                Set<Integer> usedIndexes = getUsedConstructorIndexes();
                for (int i = 0; i < constructorArgs.size(); i++) {
                    if (usedIndexes.contains(i) || constructorArgs.get(i).getName() != null) {
                        continue;
                    }
                    constructorArgs.get(i).setIndex(i);
                }
            }
            System.out.println("currentBean id  " + currentBean.getId()
                    + //"currentBean name  "+
                    //currentBean.getConstructorArgs().get(0).getName()
                    //+
                    " get type  " + currentBean.getType()
                    + "  constructor args " + currentBean.getConstructorArgs());

            beanIds.add(currentBean.getId());
            allBeans.add(currentBean);
        } else if (qName.equalsIgnoreCase("property") & readingPropertyDefState) {
            readingPropertyDefState = false;
            currentBean.setProperty(currentPropertyName, new Argument(currentPropertyType, currentPropertyDef));
        }

    }

    private Argument createArgument(Attributes attributes) {
        Argument arg = null;
        if (attributes.getValue("value") != null) {
            arg = new Argument(ArgumentType.OBJECT, attributes.getValue("value"));
        } else if ((attributes.getValue("ref") != null)) {
            arg = new Argument(ArgumentType.REFERENCE, attributes.getValue("ref"));
        }
        return arg; //null if 
    }
    
    private void processNextCollectionValue(Attributes attributes){
        if(currentPropertyType == ArgumentType.LIST || currentPropertyType == ArgumentType.SET){
            Collection<Argument> cpf = (Collection<Argument>) currentPropertyDef;
            Argument arg = createArgument(attributes);
            cpf.add(arg);
        }else if (currentPropertyType == ArgumentType.MAP || currentPropertyType == ArgumentType.PROPERTIES){
            Map<String, Argument> cpf = (Map<String, Argument>) currentPropertyDef;
            Argument arg = createArgument(attributes);
            String key = attributes.getValue("key");
            cpf.put(key, arg);
        }
    }

    private Set<Integer> getUsedConstructorIndexes() {
        List<ConstructorArg> constructorArgs = currentBean.getConstructorArgs();
        if (constructorArgs == null || constructorArgs.isEmpty()) {
            return Collections.emptySet();
        }
        Set<Integer> usedIndexes = new TreeSet<Integer>();
        for (ConstructorArg arg : constructorArgs) {
            if (arg.getIndex() >= 0) {
                usedIndexes.add(arg.getIndex());
            }
        }
        return usedIndexes;
    }
    

    public Set<BeanDefinition> getBeans() {
        return allBeans;
    }
}
